<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<script type="text/javascript">
    // 下面这样简易的定义一个函数，就可以像访问其他对象一样的访问该函数的属性
    function foo(a,b){
        return a * b;
    }

    console.log(foo.length);
    console.log(foo.constructor);

    // 而这些属性就包括有prototype属性，他的初始值是一个‘空’对象
    console.log(typeof foo.prototype);

    // 当然，我们也可以自己添加该属性，就像这样
    foo.prototype = {};

    // 而且我们还可以赋予这个空对象一些方法和属性，这并不会对foo函数本身造成什么影响；因为只有当foo()作为构造器使用时，这些属性才会起作用
</script>
<script type="text/javascript">
    // 我们来构建一个具体的构造器函数Gadget()，看看他就是是如何在新建对象时为其添加属性与方法的
    function Gadget(name,color){
        this.name = name;
        this.color = color;
        this.whatAreYou = function (){
            return 'I am a '+ this.color +''+this.name;
        }
    }

    // 当然，添加属性和方法还有另外一种形式，即通过构造器函数的prototype属性来增加该构造器所能提供的功能。下面我们为上面的构造器增加两个属性和一个方法，由于prototype属性包含的是一个对象，所以可以这样
    Gadget.prototype.price = 100;
    Gadget.prototype.rating = 3;
    Gadget.prototype.getInfo = function() {
        return 'Rating: ' + this.rating + ', price: '+this.price;
    }

    // 如果不想逐一添加，也可以定义一个对象，然后将其覆盖到之前的原型上
    // Gadget.prototype = {
    //     price: 100,
    //     rating: 3,
    //     getInfo: function(){
    //         return 'Rating: ' + this.rating + ', price: '+this.price;
    //     }
    // }

    // 再向prototype属性中添加完所有的方法和属性后，我们就可以直接用该构造器来新建对象了。例如在下面的代码中，我们用构造器Gadget新建了一个newtoy对象，然后就可以访问之前所定义的那些属性和方法了
    var newtoy = new Gadget('webcam', 'black');
    console.log(newtoy.name);
    console.log(newtoy.color);
    console.log(newtoy.whatAreYou());
    console.log(newtoy.price);
    console.log(newtoy.rating);
    console.log(newtoy.getInfo());

    // 对于原型来说，最重要的一点就是要理解他的实时性，由于在JavaScript中几乎所有对象都是通过传引用的方式来传递的，因此我们所创建的每个新对象实体中并没有一份属于自己的原型副本。这也就意味着我们可以随时更改prototype属性，并且由同一构造器创建的所有对象的prototype属性也都会同时改变（甚至还会影响到在修改之前就已经创建好的那些对象）

    // 继续例子，我们再次添加一个新方法
    Gadget.prototype.get = function(what) {
        return this[what];
    }

    // 然后我们就可以看到即便是newtoy对象在get()方法之前就已经创建了，但我们依然可以在该对象中访问到新增的方法
    console.log(newtoy.get('price'));
    console.log(newtoy.get('color'));

    // 5.1.3
    console.log('----5.1.3----');
    // 这里不使用this指针来进行对象访问，而是使用prototype进行同样的操作
    Gadget.prototype.getInfo = function() {
        return 'Rating: ' + Gadget.prototype.rating +
               ', price: ' + Gadget.prototype.price;
    };

    // 这之间会有什么不同的吗？想要解答这个问题，就必须要更深入的理解原型的工作原理
    // 我来再回到之前的那个newtoy对象上来
    var newtoy = new Gadget('webcam', 'black');

    // 当我们访问newtoy的某个属性，例如name时，JavaScript引擎会遍历该对象所有的属性，并查找出name属性。如果找到了就立即返回
    console.log(newtoy.name);

    // 那么如果我们访问rating属性又会发生什么？JavaScript引擎依然会查询newtoy对象的所有属性，但这一回它找不到一个叫做rating的属性了，接下来，脚本引擎就会去查询用于创建当前对象的构造器函数的原型（等价于我们直接访问newtoy.constructor.prototype），如果在原型中找到了该属性，就立即使用该属性
    console.log(newtoy.rating);

    // 这种方式与直接访问原型属性是一样的。每个对象都有属于自己的构造器属性，其所引用的就是用于创建该对象的那个函数，所以在这里
    console.log(newtoy.constructor === Gadget);
    console.log(newtoy.constructor.prototype.rating);

    // 现在，让我们在仔细回顾一下这个过程：
    // 首先我们知道每个对象都会有一个构造器，而原型本身也是一个对象，这意味着它必然也有一个构造器，而这个构造器又会有自己的原型。
    // 于是这种结构可能会一直不断的持续下去，并最终取决于原型链（prototype chain）的长度，但其最后一环肯定是object内建对象，因为他是最高级的父级对象
    // 事实上，如果您试着调用一下newtoy.toString()的话，由于newtoy对象及其原型中都不存在toString()方法。最后我们能调用的也只有Object对象的toString()方法了
    console.log(newtoy.toString());
</script>
</body>
</html>
